//I am Mathias Andersen (dat5) from group d502e11. 

//Compile by typing "scalac simulation.scala"
//Run program by typing "scala doSimulation"

//What I have done is to add the logic of connecting wires through gates to the Simulation class. This way you have easy access to the AfterDelay method. I'm not sure that this is the best way of doing things though. I have defined the delay constants and the Action type by way of traits. 
//I have made individual simulation classes for each of the gates, and these extend the abstract simulation class. I think this is a nice way to separate the details of the different types of simulations. 
//Otherwise I have followed the example code snippets closely. 

//When the program is run you can see that the different gates work as expected.

//I have included a short commentary for each of the classes.

//the custom type Action is used by the wire class and the simulation class,
//and it is included into these classes as a trait. 
trait actions { 
  type Action = () => Unit
}

//the constants are also added to the simulation class as traits, because I think these
//constants are really independent of the simulation constants. They are universal, and should 
//be conveniently edited in one central place. 
trait constants { 
  val InverterDelay: Int = 1
  val OrGateDelay: Int = 1
  val AndGateDelay: Int = 1
}

//The wire class keeps a list of actions (events) that are fired when the signal on the wire changes
class Wire extends actions with constants{
  private var sigVal = false
  private var actions: List[Action] = List()
  def getSignal = sigVal

  def setSignal(s: Boolean) =
    if (s != sigVal) {
      sigVal = s
      actions.foreach(action => action())
    }
 
  def addAction(a: Action) {
    actions = a :: actions; a()
  }
}

//The simulation class. It has the logic of connecting wires through gates. It is also responsible for running the simulation. This amounts to firing the sequence of events that is stored in the Agenda; a list of actions (work items) that are to be undertaken, for instance setting a particular wire to true or false. When the run method is called, the head is removed from the list of work items and the corresponding action is undertaken. 
abstract class Simulation extends actions with constants{
  case class WorkItem(time: Int, action: Action)
  private type Agenda = List[WorkItem]
  private var agenda: Agenda = List()

  private var currentTime = 0

  private def insert(ag: Agenda, item: WorkItem): Agenda =
    if (ag.isEmpty || item.time < ag.head.time) item :: ag
    else ag.head :: insert(ag.tail, item)

  def afterDelay(delay: Int)(block: => Unit) {
    val item = WorkItem(currentTime + delay, () => block)
    agenda = insert(agenda, item)
  }

  private def next() {
    agenda match {
      case WorkItem(time, action) :: rest =>
	agenda = rest; currentTime = time; action()
      case List() =>
    }
  }
  
  def run(simulName: String) {
    afterDelay(0) { println(simulName) }
    while (!agenda.isEmpty) next()
  }

  def probe(name: String, wire: Wire) {
    wire addAction { () =>
      println(name + " " + currentTime + " new_value = " + wire.getSignal)
		  }
  }

  def inverter(input: Wire, output: Wire) {
    def invertAction() {
      val inputSig = input.getSignal
      afterDelay(InverterDelay) { output setSignal !inputSig }
    }
    input addAction invertAction
  }

  def andGate(a1: Wire, a2: Wire, output: Wire) {
    def andAction() {
      val a1Sig = a1.getSignal
      val a2Sig = a2.getSignal
      afterDelay(AndGateDelay) { output setSignal (a1Sig & a2Sig) }
      }
    a1 addAction andAction
    a2 addAction andAction
  }

  def orGate(o1: Wire, o2: Wire, output: Wire) { 
    def orAction() { 
      val o1Sig = o1.getSignal
      val o2Sig = o2.getSignal
      afterDelay(OrGateDelay) { output setSignal (o1Sig | o2Sig)}
    }
    o1 addAction orAction
    o2 addAction orAction
  }
}

//this class simulates an or gate.
class OrGateSimulation extends Simulation { 
  val input1,input2,output = new Wire
  orGate(input1,input2,output)
  afterDelay(OrGateDelay) { input1.setSignal(true) }  
  probe("input1",input1)
  probe("input2",input2)
  probe("output",output)
}
//this class simulates an and gate
class AndGateSimulation extends Simulation { 
  val input1,input2,output = new Wire
  andGate(input1,input2,output)
  afterDelay(AndGateDelay) { input1.setSignal(true) }  
  afterDelay(AndGateDelay) { input2.setSignal(true) }  
  probe("input1",input1)
  probe("input2",input2)
  probe("output",output)
}
//this class simulates an inverter.
class InverterSimulation extends Simulation { 
  val input,output = new Wire
  output.setSignal(true) //for system to be in consistent state at beginning. 
  inverter(input,output)
  afterDelay(InverterDelay) { input.setSignal(true) }  
  probe("input",input)
  probe("output",output)
}
//This is the work horse of the application. It news up a case of each of the simulations and runs them. It extends the abstract class App so that the lines of code in the body will be executed when the program runs. 
object doSimulation extends App { 
  var orGateSimulation: OrGateSimulation = new OrGateSimulation
  orGateSimulation.run("***Or-gate-simulation started***")
  println("***simulation ended***")
  var andGateSimulation: AndGateSimulation = new AndGateSimulation
  andGateSimulation.run("***And-gate-simulation started***")
  println("***simulation ended***")
  var inverterSimulation: InverterSimulation = new InverterSimulation
  inverterSimulation.run("***Inverter-gate-simulation started***")
  println("***simulation ended***")
}
